#include "NameServer.h"

#include "misc/Event.h"
#include "misc/Interface.h"
#include "nameserver/net/nsMsg.h"
#include "nameserver/net/Announce.h"
#include "nameserver/net/KeepAlive.h"
#include "nameserver/net/Ack.h"
#include "nameserver/ui/nsuiBase.h"
#include "nameserver/misc/nsConfig.h"
#include <Poco/ScopedLock.h>
#include <Poco/String.h>
#include <Poco/Net/NetworkInterface.h>
#include <sstream>
#include <iomanip>
/*********************************************************/
void NameServer::run()
{
	Event* process = Event::Instance(PROCESS_EVENT);
	Event* quit = Event::Instance(QUIT_EVENT);
	Interface& ui = Interface::Instance();

	mutex.lock();
	while ( !quitFlag ) {
		mutex.unlock();
		process->wait();		// Wait for network traffic or user input

		// Process ui commands
		std::string uiCmd;
		if ( ui.peek(uiCmd) ) {
			ui.pop();
			nsuiBase* cmd = nsuiBase::parse(uiCmd);
			if ( cmd ) {
				cmd->execute();
			}
		}

		// Empty any multicast packets we are receiving
		while ( multicast->available() ) {
			std::string		msg = multicast->pop();
			try {
				nsMsg* p = nsMsg::deserialize(msg);
				if ( p ) {
					p->execute();
					delete( p );
				}
			} catch(...) {
				ui << "Received broadcast junk: " << msg << std::endl;
				ui.flush();
				continue;
			}
		}

		// Check for unicast messages
		while ( unicast->available() ) {
			Poco::Net::SocketAddress	addr;
			std::string msg = unicast->pop(addr);
			try {
				nsMsg* p = nsMsg::deserialize(msg);
				if ( p ) {
					p->setSenderAddr(addr);
					p->execute();

					// update the keep alive timer
					mutex.lock();
					keepalive[addr.toString()] = Poco::Timestamp();
					mutex.unlock();
					delete( p );
				}
			} catch(std::exception& e) {
				mutex.unlock();
				ui << e.what() << std::endl;
				ui.flush();
				continue;
			} catch(...) {
				mutex.unlock();
				ui << "Received unicast junk: " << msg << std::endl;
				ui.flush();
				continue;
			}
		}

		processKeepAlive();
		broadcastPresence();

		mutex.lock();
	}
	mutex.unlock();

	quit->set();
}
/*********************************************************/
void NameServer::broadcastPresence()
{
	Interface& ui = Interface::Instance();
	nsConfig& cfg = nsConfig::Instance();

	mutex.lock();
	// Keep broadcasting our presence on the multicast group
	if ( refreshTime.elapsed() >= (cfg.getBroadcastFrequency()*1000) ) {
		int port;
		std::string addr;
		unicast->getLocalAddress(addr, port);

		std::ostringstream ostr;
		ostr << addr << ":" << port;
		Announce	msg;
		msg.setAddress(ostr.str());
		multicast->send(msg.serialize());

		ui << "Broadcasting Presence: " << addr << ":" << port << std::endl;
		ui.flush();

		refreshTime = Poco::Timestamp();
	}
	mutex.unlock();
}
/*********************************************************/
void NameServer::processKeepAlive()
{
	Interface& ui = Interface::Instance();
	nsConfig& cfg = nsConfig::Instance();

	// Check for inactivity among clients & ring nodes
	mutex.lock();
	if ( keepAliveCheck.elapsed() >= (cfg.getKeepAliveCheckDelay()*1000)) {
		std::map<std::string, Poco::Timestamp>::iterator it;

		for ( it = keepalive.begin(); it != keepalive.end(); it++ ) {
			if ( it->second.elapsed() >= (cfg.getKeepAliveRemoveDelay()*1000)) {
				ui << "Node: " << it->first << " has been inactive for: " << it->second.elapsed() << " µs. Removing node" << std::endl;
				ui.flush();
				remove(it->first);
				for ( unsigned int i=0; i<rings.size(); i++ ) {
					rings[i].removeClient(it->first);
					rings[i].removeNode(it->first);
				}
			} else if ( it->second.elapsed() >= (cfg.getKeepAliveTime()*1000) ) {
				DISCONNECTED_MAP::iterator dit = disconnected.find(it->first);
				if ( dit == disconnected.end() ) {
					ui << "Node: " << it->first << " has been inactive for: " << it->second.elapsed() << " µs. Sending KeepAlive packet" << std::endl;
					ui.flush();

					// Send a keepalive packet
					KeepAlive ka;
					sendTo(&ka, it->first);
				} else {
					if ( dit->second.elapsed() >= 5000000 ) {
						disconnected.erase(dit);
						remove(it->first);
					}
				}
			}
		}
		keepAliveCheck = Poco::Timestamp();
	}
	mutex.unlock();
}
/*********************************************************/
void NameServer::remove(const std::string& addr)
{
	mutex.lock();
	// Remove any messages that are awaiting an ack from this node
	ACK_MAP::iterator ita = packets.find(addr);
	if ( ita != packets.end() ) {
		packets.erase(ita);
	}

	// Remove the keep alive record
	KEEPALIVE_MAP::iterator kit = keepalive.find(addr);
	if ( kit != keepalive.end() ) {
		keepalive.erase(kit);
	}
	mutex.unlock();
}
/*********************************************************/
std::string NameServer::getLocalAddress() const
{
	mutex.lock();
	std::string host;
	int port;
	unicast->getLocalAddress(host, port);

	std::ostringstream ostr;
	ostr << host << ":" << port;
	mutex.unlock();
	return ostr.str();
}
/*********************************************************/
void NameServer::send(const nsMsg* msg)
{
	mutex.lock();
	int port;
	std::string host;
	unicast->getResponseAddress(host, port);
	std::ostringstream ostr;
	ostr << host << ":" << port;
	std::string m = msg->serialize();
	unsigned long id = msg->getID();

	// Add the message to the waiting for Ack queue & send it
	packets[ostr.str()][id] = m;	
	unicast->send(m);
	mutex.unlock();
}
/*********************************************************/
void NameServer::sendTo(const nsMsg* msg, const std::string& target)
{
	mutex.lock();	
	std::string m = msg->serialize();
	packets[target][msg->getID()] = m;
	unicast->sendTo(m, target);
	mutex.unlock();
}
/*********************************************************/
void NameServer::broadcast(const nsMsg* msg)
{
	mutex.lock();
	multicast->send(msg->serialize());
	mutex.unlock();
}
/*********************************************************/
std::string NameServer::getLoad() const
{
	mutex.lock();
	std::ostringstream ostr;
	ostr << "Ring Load Overview: " << std::endl;
	ostr << "--------------------" << std::endl;
	ostr << std::left << std::setw(30) << "Ring" << std::setw(30) << "Load" << std::endl;
	for ( unsigned int i=0; i<rings.size(); i++ ) {
		ostr << std::left << std::setw(30) << rings[i].getName() << std::setw(30) << rings[i].load() << std::endl;
	}
	mutex.unlock();
	return ostr.str();
}
/*********************************************************/
std::string NameServer::getLoad(const std::string& ringName) const
{
	mutex.lock();
	bool found = false;
	std::string result;
	for ( unsigned int i=0; i<rings.size(); i++ ) {
		if ( Poco::icompare(rings[i].getName(), ringName) == 0 ) {
			found = true;
			result = rings[i].loadOverview();
		}
	}

	if ( !found ) {
		result = "No ring found with specified name\n";
	}

	mutex.unlock();
	return result;
}
/*********************************************************/
std::string NameServer::getActivity() const
{
	mutex.lock();
	std::ostringstream ostr;
	KEEPALIVE_MAP::const_iterator it;
	ostr << std::left << std::setw(30) << "Address" << std::setw(30) << "Elapsed time since last packet (ms)" << std::endl;
	ostr << "--------------------------------------------------------------" << std::endl;
	for ( it = keepalive.begin(); it != keepalive.end(); it++ ) {
		ostr << std::setw(30) << it->first << std::setw(30) << (it->second.elapsed()/1000) << std::endl;
	}
	mutex.unlock();
	return ostr.str();
}
/*********************************************************/
unsigned long NameServer::getID()
{
	mutex.lock();
	unsigned long a = counter;
	counter++;
	mutex.unlock();
	return a;
}
/*********************************************************/
void NameServer::wait()
{
	Event* quit = Event::Instance(QUIT_EVENT);
	quit->wait();
}
/*********************************************************/
void NameServer::start()
{
	Poco::ScopedLock<Poco::Mutex>	sLock(mutex);
	nsConfig& cfg = nsConfig::Instance();
	Event* process = Event::Instance(PROCESS_EVENT);
	Event* quit = Event::Instance(QUIT_EVENT);
	quitFlag = false;
	quit->reset();
	process->reset();

	// Validate the network interface
	Poco::Net::NetworkInterface iface = Poco::Net::NetworkInterface::forName(cfg.getNetworkInterface());
	unsigned int av_port = UDPNet::getAvailablePort(iface.address().toString(), MIN_PORT_RANGE_, MAX_PORT_RANGE_);
	unicast = new( UDPNet )(iface.address().toString(), av_port);
	unicast->start(PROCESS_EVENT);

	// Start the multicast class
	multicast = new( MultiNet )(cfg.getNetworkInterface());
	multicast->start(PROCESS_EVENT);
	multicast->joinGroup(cfg.getMulticastGroup().host().toString(), cfg.getMulticastGroup().port());	// Join a multicasting group
	multicast->setLoopback(true);

	// Set a timer to activate processing in order to broadcast our presence
	clock = new Timer(PROCESS_EVENT, 1000, true);
	refreshTime = Poco::Timestamp();
	keepAliveCheck = Poco::Timestamp();

	// Start the interface
	Interface& ui = Interface::Instance();
	ui.start(PROCESS_EVENT);

	// Start the local thread
	lThread.start((*this));

	// Wait until the thread has succesfully started
	usleep(200);

	mutex.lock();
	int port;
	std::string addr;
	unicast->getLocalAddress(addr, port);
	ui << "NameServer Started: " << std::endl;
	ui << "Address: " << addr << ":" << port << std::endl;
	ui << "Multicast group: " << cfg.getMulticastGroup().host().toString() << ":" << cfg.getMulticastGroup().port() << std::endl;
	ui.flush();
	mutex.unlock();
}
/*********************************************************/
void NameServer::stop()
{
	Interface& ui = Interface::Instance();

	// Set a processing event & the quit flag so the nameserver notices it needs to quit
	Event* process = Event::Instance(PROCESS_EVENT);
	ui << "Stopping NameServer Core... ";
	ui.flush();
	mutex.lock();
	quitFlag = true;
	process->set();
	mutex.unlock();
	unsigned int time = 0;
	while ( lThread.isRunning() && (time < 5000)) {
		lThread.tryJoin(25);
		time += 25;
	}
	ui << "done" << std::endl;
	ui.flush();

	// Stop the network classes
	ui << "Stopping unicast network sockets... ";
	ui.flush();
	if ( unicast ) {
		unicast->stop();
		delete( unicast );
	}
	ui << "done" << std::endl;
	ui.flush();

	ui << "Stopping multicast network sockets... ";
	ui.flush();
	if ( multicast ) {
		multicast->stop();
		delete( multicast);
	}
	ui << "done" << std::endl;
	ui.flush();

	// Stop the timer
	clock->stop();
	delete( clock );

	// Stop the interface
	ui << "Stopping user interface...";
	ui.flush();
	ui.stop();
}
/*********************************************************/
NameServer& NameServer::Instance( ) 
{
	static NameServer pInstance;
	return pInstance; 			// return the address of the sole instance
}
/*********************************************************/
